using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using System.IO;
using System.Xml.Serialization;

namespace Project
{
    /// <summary>
    /// This is the main type for your game
    /// </summary>
    public class Game : EngineState, GameState
    {

        protected Engine engine;
        protected ContentManager content;
        protected SoundManager soundManager;
        protected GraphicsDeviceManager graphics;
        protected SpriteBatch spriteBatch;
        protected SpriteFont statsFont;

        protected Texture2D crosshair;

        protected Client client;
        protected HighScore highScore;

        protected Camera camera = new Camera();
        protected CollisionManager collisionManager;
        protected Scene scene;

        protected LinkedList<GameEntity> entities;
        private LinkedList<GameEntity> toSpawn = new LinkedList<GameEntity>();
        private LinkedList<GameEntity> toRemove = new LinkedList<GameEntity>();
        protected Player player;

        private Song currentSong;

        Int64 seconds;
        Int64 milliseconds = 0;
        float interval = 1000f;

        public int level { get; protected set; }

        private bool inGame = true;

        public Game(int level)
            : base()
        {
            this.level = level;
        }

        public void initialize(Engine e, ContentManager cm, GraphicsDeviceManager gdm)
        {
            engine = e;
            content = cm;
            graphics = gdm;
            soundManager = new SoundManager(content);
            client = new Client();

            highScore = new HighScore(-1, "", -1);
            client.getHighScoreAsync(level, out highScore);
            entities = new LinkedList<GameEntity>();
        }

        public void destroy()
        {
            foreach (GameEntity e in entities)
            {
                e.destroy(this);
            }
        }

        public void loadContent()
        {
            spriteBatch = new SpriteBatch(graphics.GraphicsDevice);
            statsFont = content.Load<SpriteFont>("fonts/Arial");

            scene = new Scene(graphics.GraphicsDevice, content);

            // Load level data
            Map map = new Map();
            map.load(content, "levels/level1/map", "levels/level1/rock_texture", "palate", "rockbump");

            scene.renderables.Add(map);

            CollisionMap cmap = CollisionMap.loadFromFile(new System.IO.StreamReader("Content/levels/level1/map.col"));
            collisionManager = new CollisionManager(cmap);
            spawnEntities("Content/levels/level1/spawn.txt");

            camera.AspectRatio = graphics.GraphicsDevice.Viewport.AspectRatio;

            currentSong = content.Load<Song>("sounds\\dukenukem");
            MediaPlayer.Volume = 0.1f;
            MediaPlayer.Play(currentSong);


            crosshair = content.Load<Texture2D>("images\\crosshair");

            engine.setMouseVisible(false);
        }

        public void update(GameTime gameTime)
        {
            // spawn queued entities
            foreach (GameEntity e in toSpawn)
            {
                entities.AddLast(e);
            }
            toSpawn.Clear();
            // update entities
            foreach (GameEntity e in entities)
            {
                if (!e.update(gameTime, this))
                {
                    toRemove.AddLast(e);
                }
                if(!inGame) {
                    return;
                }
            }
            // remove dead entities
            foreach (GameEntity e in toRemove)
            {
                e.destroy(this);
                entities.Remove(e);
            }
            toRemove.Clear();

            // count time??  why don't we just use the total time whenever we need it?
            milliseconds += (long)gameTime.ElapsedGameTime.TotalMilliseconds;
            if (milliseconds >= interval)
            {
                seconds += 1;
                milliseconds = 0;
            }

            // update the player's location in the sound manager to give us 3d sound
            soundManager.listenerPosition = player.position;
        }

        public void draw(GameTime gameTime)
        {
            camera.view = Matrix.CreateTranslation(player.position);
            camera.view *= Matrix.CreateFromQuaternion(Quaternion.Inverse(player.orientation));

            scene.render(gameTime, camera, player.position);

            spriteBatch.Begin();
            Rectangle rectSafeArea = graphics.GraphicsDevice.Viewport.TitleSafeArea;
            int xOffset = rectSafeArea.X + rectSafeArea.Width / 2 - 25;
            int yOffset = rectSafeArea.Y + rectSafeArea.Height / 2 - 25;
            spriteBatch.Draw(crosshair, new Rectangle(xOffset, yOffset, 50, 50), Color.White);
            spriteBatch.End();

            // TODO Remove this
            // drawText("Position: " + player.position.ToString(), 10, 20);
            drawText("Lives: " + player.lives, rectSafeArea.Width - 100, rectSafeArea.Height - 30);
            if(highScore.score > -1)
                drawText("Best Time: " + highScore.score, 10, 20);
            drawText("Your Time: " + seconds.ToString(), 10, rectSafeArea.Height - 30);
        }

        private void drawText(string text, float x, float y)
        {
            //lots o' stuff from Microsoft's FuelCell example
            float xOffsetText, yOffsetText;

            string str1 = text;
            Rectangle rectSafeArea;

            //Calculate str1 position
            rectSafeArea = graphics.GraphicsDevice.Viewport.TitleSafeArea;

            xOffsetText = rectSafeArea.X;
            yOffsetText = rectSafeArea.Y;

            Vector2 strSize = statsFont.MeasureString(str1);
            Vector2 strPosition =
                new Vector2((int)xOffsetText + x, (int)yOffsetText + y);

            spriteBatch.Begin();
            spriteBatch.DrawString(statsFont, str1, strPosition, Color.White);
            spriteBatch.End();

            //re-enable depth buffer after sprite batch disablement
            DepthStencilState dss = new DepthStencilState();
            dss.DepthBufferEnable = true;
            graphics.GraphicsDevice.DepthStencilState = dss;
        }

        public void onLose()
        {
            inGame = false;

            MediaPlayer.Stop();
            engine.setState(new GameOver());
        }

        public void onWin()
        {
            inGame = false;

            MediaPlayer.Stop();
            updateHighScores("highscores.lst");
            client.sendHighScoreAsync(new HighScore(level, Environment.UserName, seconds));
            engine.setState(new WinScreen());
        }

        public T loadAsset<T>(String assetName)
        {
            // The content manager should already cache loaded objects, so this should be quite fast
            return content.Load<T>(assetName);
        }

        public CollisionManager getCollisionManager()
        {
            return collisionManager;
        }

        public SoundManager getSoundManager()
        {
            return soundManager;
        }

        public Scene getScene()
        {
            return scene;
        }

        public GraphicsDevice getGraphics()
        {
            return graphics.GraphicsDevice;
        }

        public void spawnEntity(GameEntity entity)
        {
            entity.init(this);
            toSpawn.AddLast(entity);
        }

        protected void spawnEntities(String filename)
        {
            //Thanks MSDN
            try
            {
                //System.IO.Stream stream = TitleContainer.OpenStream(filename);
                System.IO.StreamReader reader = new System.IO.StreamReader(filename);
                // use StreamReader.ReadLine or other methods to read the file data

                String entity;
                if (System.IO.File.Exists(filename))
                {
                    while (!reader.EndOfStream)
                    {
                        float posx = 0, posy = 0, posz = 0, dirx = 0, diry = 0, dirz = 1, upx = 0, upy = 1, upz = 0;
                        String line = reader.ReadLine();
                        String[] lineArray = line.Split(" ".ToCharArray());
                        entity = lineArray[0];
                        posx = float.Parse(lineArray[1]);
                        posy = float.Parse(lineArray[2]);
                        posz = float.Parse(lineArray[3]);

                        if (lineArray.Length > 4)
                        {
                            dirx = float.Parse(lineArray[4]);
                            diry = float.Parse(lineArray[5]);
                            dirz = float.Parse(lineArray[6]);
                        }

                        if (lineArray.Length > 7)
                        {
                            upx = float.Parse(lineArray[7]);
                            upy = float.Parse(lineArray[8]);
                            upz = float.Parse(lineArray[9]);
                        }

                        GameEntity spawn = null;
                        Vector3 posVect = new Vector3(posx, posy, posz);
                        Vector3 dirVect = new Vector3(dirx, diry, dirz);
                        Vector3 upVect = new Vector3(upx, upy, upz);
                        posVect.Normalize();
                        dirVect.Normalize();
                        upVect.Normalize();

                        Quaternion orientation = Quaternion.CreateFromRotationMatrix(Matrix.CreateLookAt(Vector3.Zero, dirVect, upVect));

                        switch (entity)
                        {
                            case "player":
                                Console.WriteLine("player initialized");
                                this.player = new Player(new Vector3(posx, posy, posz), orientation);
                                spawn = this.player;
                                break;
                            case "spiky":
                                // Console.WriteLine("Spiky Ball initialized");
                                // spawn = new SpikeBall(new Vector3(posx, posy, posz), orientation);
                                break;
                            case "spikyLauncher":
                                Console.WriteLine("Spiky Ball Launcher initialized");
                                spawn = new SpikeLauncher(new Vector3(posx, posy, posz), orientation);
                                break;
                            case "mine":
                                Console.WriteLine("Mine initialied");
                                spawn = new Mine(new Vector3(posx, posy, posz), orientation);
                                break;
                        }

                        spawnEntity(spawn);
                    }
                }
                else
                {
                    Console.WriteLine("Problem");
                }
                //stream.Close();
            }
            catch (System.IO.FileNotFoundException)
            {
            }
            catch (IndexOutOfRangeException)
            {
            }
        }

        private void updateHighScores(String filename)
        {
            int[] data;

            // Open the file
            FileStream stream = File.Open(filename, FileMode.OpenOrCreate,FileAccess.Read);
            try
            {

                // Read the data from the file
                XmlSerializer serializer = new XmlSerializer(typeof(int[]));
                data = (int[])serializer.Deserialize(stream);
            }
            finally
            {
                // Close the file
                stream.Close();
            }

            File.WriteAllText(filename, "");
            if (seconds < data[level-1])
                data[level - 1] = (int) (seconds);
            stream = File.Open(filename, FileMode.Open,FileAccess.ReadWrite);
            try
            {
                // Convert the object to XML data and put it in the stream
                XmlSerializer serializer = new XmlSerializer(typeof(int[]));
                serializer.Serialize(stream, data);
            }
            finally
            {
                // Close the file
                stream.Close();
            }
        }
    }
}
